#include "http_client.h"
#include <curl/curl.h>
// ❌VS Code提示：cannot open source file "cjson/cJSON.h"C/C++(1696)
// 原因分析：VS Code 的 IntelliSense（C/C++ 扩展）找不到头文件路径，make 编译能通过，VS Code 仍会红线提示
// VS Code 的 IntelliSense 使用的是它自己的头文件搜索路径（includePath），而不是 CMake 的 include_directories()。
// 所以即便 CMake 已经配置好，VS Code仍需要在 .vscode/c_cpp_properties.json 的"includePath"配置 cJSON 的
// 头文件路径：F:/c/EmbeddedDevelopment/msys64/mingw64/include/cjson
#include <cjson/cJSON.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "json_analysis.h"
#include "utils/str_util.h"
#include "utils/file_util/file_util.h"
#include "include/common.h"

typedef struct {
    char *memory_data;
    size_t size;
} MemoryBlock;

/**
 * curl回调
 * @param contents	libcurl 传来的数据指针
 * @param size	每个数据单元的大小（通常为 1）
 * @param nmemb	数据单元数量
 * @param userp	通过 CURLOPT_WRITEDATA 传进去的自定义指针（可用来传结构体、buffer等）
 * @return 实际处理的字节数，否则 libcurl 会认为写入失败
 */
static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) {
    size_t total = size * nmemb;
    MemoryBlock *mem = (MemoryBlock *)userp;

    char *ptr = realloc(mem->memory_data, mem->size + total + 1);
    if (ptr == NULL) { // 内存不足
        printf("Out of memory");
        return 0;
    }

    mem->memory_data = ptr;
    memcpy(&(mem->memory_data[mem->size]), contents, total);
    mem->size += total;
    mem->memory_data[mem->size] = '\0';

    return total;
}

/**
 * http请求
 * @param url	api地址
 * @param method	请求方式，get/post
 * @param headers	请求头（HTTP Headers）
 * @param form_urlencoded	普通表单（text）Content-Type: application/x-www-form-urlencoded
 * @param useragent	HTTP 请求头里的User-Agent 字段，告诉服务器：“我是谁（浏览器 / 客户端 / 应用）”。
 * 常见 User-Agent 示例
    客户端类型	User-Agent 示例
    Chrome 浏览器	Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36
    iOS 应用	MyiOSApp/2.3.1 (iPhone; iOS 17.1)
    Android 应用	MyAndroidApp/2.3.1 (Android 14; Pixel 7)
    curl 默认	curl/8.5.0
    自定义 API 客户端	MyCompanyAPIClient/1.0 (+https://example.com)
 *
 *
 * @return cJSON请求结果
 */
static cJSON *http_request(const char *const url,
                          enum http_client_req_method method,
                          struct curl_slist *headers,
                          const char *form_urlencoded,
                          struct curl_httppost *form_data,
                          const char *const useragent) {

    MemoryBlock chunk = {0};
    chunk.memory_data = malloc(1);  // 初始空字符串
    chunk.size = 0;
    // 对于单线程无需curl_global_init，而对于多线程、含HTTPS请求时，则需要curl_global_init
    // 初始化内部的全局状态（如SSL、DNS、全局互斥锁等）
    curl_global_init(CURL_GLOBAL_DEFAULT);
    CURL *curl = curl_easy_init();
        // 2. 设置 POST 方法
    if (curl) {
        // 设置目标 URL
        curl_easy_setopt(curl, CURLOPT_URL, url);

        switch (method)
        {
        case get:
            curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
            break;
        case post:
            curl_easy_setopt(curl, CURLOPT_POST, 1L);
            // 设置POST数据即RequestBody参数，对于URL参数直接附加在url上即可
            if (form_urlencoded != NULL)
            {
                printf("form_urlencoded: %s\n", form_urlencoded);
                curl_easy_setopt(curl, CURLOPT_POSTFIELDS, form_urlencoded);
            }
            if (form_data != NULL) {
                //form_data是指向链表结构体的指针类型，打印指针如果输出是 (nil) 或 0x0，那说明curl_formadd()没创建成功
                printf("form_data pointer: %p\n", (void*)form_data);
                // 设置 HTTP POST multipart/form-data表单数据
                curl_easy_setopt(curl, CURLOPT_HTTPPOST, form_data);
            }
            break;

        default:
            break;
        }

        if (headers != NULL)
        {
            curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
        }
        if (useragent != NULL)
        {
            // ✅请求头增加User-Agent
            // struct curl_slist *headers = NULL;
            // headers = curl_slist_append(headers, "User-Agent: pan.baidu.com");
            curl_easy_setopt(curl, CURLOPT_USERAGENT, useragent);
        }


        // 在接收到服务器响应数据时，调用自定义的函数（write_callback）来处理这些数据。
        // 如果不设置 CURLOPT_WRITEFUNCTION，libcurl 默认会把接收到的数据直接输出到 stdout即printf("%s\n", response);
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);// 把 chunk 传给回调函数
        // 打开详细调试输出，观察 TLS 握手过程
        curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);

        if (is_windows_sys()) {
            // 在Windows系统错误❌：closing connection #0 curl failed: Problem with the SSL CA cert (path? access rights?)
            // 原因：libcurl在Windows上找不到或无法访问CA证书文件。macOS系统自带证书，所以正常，但Windows需要自己指定。
            // ✅解决办法：设置CA根证书路径（等价于 PHP 的 curl.cainfo），让 HTTPS 请求能正确验证服务器证书。
            // 参考：https://stackoverflow.com/a/61608965/10569504
            // 1️⃣下载CA根证书： https://curl.haxx.se/ca/cacert.pem
            // 2️⃣放置CA根证书在项目路径：${workspaceFolder}/assets/certs/cacert.pem
            // 3️⃣在launch.json的environment增加SSL_CERT_FILE字段，值为CA根证书的路径，实现将其注入为编译或运行环境变量
            // 4️⃣在C代码里用getenv("SSL_CERT_FILE")取值，然后通过curl_easy_setopt设置CA根证书路径
            printf("Running on Windows\n");
            const char *cert_path = getenv("SSL_CERT_FILE");//通过环境变量或配置文件读取
            if (cert_path) {
                curl_easy_setopt(curl, CURLOPT_CAINFO, cert_path);
            } else {
                printf("Warning: SSL_CERT_FILE not set.\n");
            }
        } else {
            printf("Running on MacOS or Linux System\n");
        }

        // 使用 OpenSSL 进行证书验证
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);  // 验证服务器证书
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);  // 验证主机名
        // 关闭证书验证，用于测试
        // curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);  // 0L关闭验证服务器证书
        // curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);  // 0L关闭验证主机名

        // 如果有自签证书，可指定 CA 路径
        // curl_easy_setopt(curl, CURLOPT_CAINFO, "/path/to/ca.crt");

        CURLcode res = curl_easy_perform(curl);
        // curl_easy_cleanup与cJSON的内存无关。可以安全地在返回json前调用它，不会影响JSON对象的使用。
        curl_easy_cleanup(curl);// ✅必须调用，释放CURL内部资源的释放，例如CURL handler内部的句柄、socket缓冲区、SSL资源等；

        if (res != CURLE_OK) {
            printf("curl failed: %s\n", curl_easy_strerror(res));
        } else {
            printf("Response:\n%s\n", chunk.memory_data);

            // 使用 cJSON 解析响应
            cJSON *json = cJSON_Parse(chunk.memory_data);
            if (json == NULL) {
                fprintf(stderr, "Failed to parse JSON\n");
            } else {

                // ❌ cJSON_Delete(json);
                // 不调用cJSON_Delete(result)，❌否则返回的指针将指向一块已经被释放的内存，
                // ❌后续任何访问都会导致段错误（Segmentation fault），改成由调用者在解析处理完之后再释放
                // 返回HTTP请求结果json对象

                // ❌ free(chunk.memory_data);
                // free不会影响cJSON *json对象本身的存在，❌但会影响到json内部字符串字段的内容
                // ❌ 例如访问json->child->valuestring就变成悬空指针(dangling pointer)
                // 后续printf("%s\n", name->valuestring)就会❌崩溃
                // ✅ 只需调用cJSON_Delete(json)即可释放相关内存，无需额外调用free

                return json;
            }
        }
    }

    // 请求失败，返回NULL
    free(chunk.memory_data);
    return NULL;
}



/**
 * 纯URL链接请求
 * @param url	URL链接
 * @return cJSON请求结果
 */
cJSON *get_query(const char *const url) {
    return http_request(url, get, NULL, NULL, NULL, NULL);
}


/**
 * get请求
 * @param url	api地址
 * @param headers	请求头（HTTP Headers）
 * @param useragent	HTTP 请求头里的User-Agent 字段，告诉服务器：“我是谁（浏览器 / 客户端 / 应用）”。
 * @return cJSON请求结果
 */
cJSON * http_get_req(const char *const url, struct curl_slist *headers, const char *const useragent) {
    return http_request(url, get, headers, NULL, NULL, useragent);
}

/**
 * post请求
 * @param url	api地址
 * @param headers	请求头（HTTP Headers）
 * @param post_fields	POST 表单数据
 * @param useragent	HTTP 请求头里的User-Agent 字段，告诉服务器：“我是谁（浏览器 / 客户端 / 应用）”。
 * @return cJSON请求结果
 */
cJSON * http_post_req(const char *const url, struct curl_slist *headers, const char *post_fields, const char *const useragent) {
    return http_request(url, post, headers, post_fields, NULL, useragent);
}


/**
 * upload file上传文件
 * @param url	api地址
 * @param form_data_name 表单字段名，上传文件字段名称："file"或者"image"
 * @param file_path 文件路径："/apps/AppName/filename.jpg"
 * @param useragent	HTTP 请求头里的User-Agent 字段，告诉服务器：“我是谁（浏览器 / 客户端 / 应用）”。
 *
 * @return _Bool 上传文件结果
 */
cJSON * http_post_upload_file_req(const char *const url, char *form_data_name, char *file_path, const char *const useragent) {
    // 判断file_path文件是否存在，若为否直接返回失败
    if (!is_file_exist(file_path)) {
        // ❌文件不存在

        // char *err_json = "{\"errno\": 404,\"result\": \"file not exist\",\"message\": \"file not exist\"}";
        // cJSON *json = cJSON_Parse(err_json);
        // return json;

        char err_buf[256];
        snprintf(err_buf, sizeof(err_buf), "{\"errno\":%d,\"result\":\"not found\",\"message\":\"file not exist\"}", ERR_NOT_FOUND);
        return cJSON_Parse(err_buf);
    }

    // 设置 multipart/form-data 表单
    struct curl_httppost *formpost = NULL;
    struct curl_httppost *lastptr = NULL;

    // 添加 file 字段，对应 curl -F "file=@xxx"
    // 第一个formpost：表单链表的起始指针，第二个lastptr：链表的最后一个指针（便于追加）
    // ❌第二个参数如果传NULL，在某些版本中会引发未定义行为或Segmentation fault导致崩溃
    // 因为last_post应该是一个真实的struct curl_httppost *变量地址
    curl_formadd(&formpost, &lastptr,
                    CURLFORM_COPYNAME, form_data_name,
                    CURLFORM_FILE, file_path,
                    CURLFORM_END);

    cJSON *result = http_request(url, post, NULL, NULL, formpost, useragent);

    curl_formfree(formpost); // 释放资源
    return result;
}
